package org.springframework.security.integration;

import java.util.List;

import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;

import net.sourceforge.jwebunit.junit.WebTester;

import org.mortbay.jetty.Server;
import org.mortbay.jetty.servlet.ServletHolder;
import org.mortbay.jetty.webapp.WebAppContext;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.util.StringUtils;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.DispatcherServlet;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;

/**
 * Base class which allows the application to be started with a particular Spring application
 * context. Subclasses override the <tt>getContextConfigLocations</tt> method to return
 * a list of context file names which is passed to the <tt>ContextLoaderListener</tt> when
 * starting up the webapp.
 *
 * @author Luke Taylor
 */
public abstract class AbstractWebServerIntegrationTests {
    private Server server;
    private final Object SERVER_LOCK = new Object();
    protected final WebTester tester = new WebTester();

    /**
     * Override to set the application context files that should be loaded or return null
     * to use web.xml.
     */
    protected abstract String getContextConfigLocations();

    protected String getContextPath() {
        return "/testapp";
    }

    @BeforeClass
    public void startServer() throws Exception {
        synchronized(SERVER_LOCK) {
            if (server == null) {
                //System.setProperty("DEBUG", "true");
                //System.setProperty("VERBOSE", "true");
                //System.setProperty("IGNORED", "true");
                server = new Server(0);
                server.addHandler(createWebContext());
                server.start();
            }
        }
    }

    @SuppressWarnings("unchecked")
    private WebAppContext createWebContext() {
        String webappDir = System.getProperty("webapp.dir");

        WebAppContext webCtx = new WebAppContext(webappDir == null ? "src/main/webapp" : webappDir, getContextPath());

        if (StringUtils.hasText(getContextConfigLocations())) {
            webCtx.addEventListener(new ContextLoaderListener());
            webCtx.addEventListener(new HttpSessionEventPublisher());
            webCtx.getInitParams().put("contextConfigLocation", getContextConfigLocations());
        }

        ServletHolder servlet = new ServletHolder();
        servlet.setName("testapp");
        servlet.setClassName(DispatcherServlet.class.getName());
        webCtx.addServlet(servlet, "*.htm");

        return webCtx;
    }

    @AfterClass
    public void stopServer() throws Exception {
        synchronized(SERVER_LOCK) {
            if (server != null) {
                server.stop();
            }
            server = null;
        }
    }

    @BeforeMethod
    public void initializeTester() {
        tester.getTestContext().setBaseUrl(getBaseUrl());
    }

    @AfterMethod
    public void resetWebConversation() {
        tester.closeBrowser();
        tester.setTestContext(null);
    }

    protected final String getBaseUrl() {
        int port = server.getConnectors()[0].getLocalPort();
        return "http://localhost:" + port + getContextPath() + "/";
    }

    protected final Object getBean(String beanName) {
        return getAppContext().getBean(beanName);
    }

    protected final WebApplicationContext getAppContext() {
        ServletContext servletCtx = ((WebAppContext)server.getHandler()).getServletContext();
        WebApplicationContext appCtx =
                WebApplicationContextUtils.getRequiredWebApplicationContext(servletCtx);
        return appCtx;
    }

    @SuppressWarnings("unchecked")
    protected Cookie getRememberMeCookie() {
        List<Cookie> cookies = (List<Cookie>) tester.getTestingEngine().getCookies();
        for (Cookie c : cookies) {
            if (c.getName().equals("SPRING_SECURITY_REMEMBER_ME_COOKIE")) {
                return c;
            }
        }
        return null;
    }

    protected final void submit() {
        tester.submit();
    }

    protected final void beginAt(String url) {
        tester.beginAt(url);
    }

    protected final void setTextField(String name, String value) {
        tester.setTextField(name, value);
    }

    protected final void assertFormPresent() {
        tester.assertFormPresent();
    }

    protected final void assertTextPresent(String text) {
        tester.assertTextPresent(text);
    }

    // Security-specific utility methods

    protected void login(String username, String password) {
        assertFormPresent();
        setTextField("username", username);
        setTextField("password", password);
        submit();
    }
}
